Zbiór danych:
Wykorzystamy zbiór danych medycznych UCI Heart Disease, który zawiera wiek, płeć oraz wyniki badań medycznych pacjenta. Targetem jest ocena występowania wieńcowej choroby serca poprzez ocenę zwężenia naczyń wieńcowych (brak choroby - 0, choroba - 1). Zmienne kategoryczne (cp, thal oraz slope) zostały przetworzone za pomocą One-hot encoding, stąd w ramce danych pojawiły nam się zmienne z indeksami (np. thal_fd, thal_rd, thal_n).
Model:
Jako model wykorzystany zostanie Random Forest.
import pickle
import dalex as dx
import pandas as pd
import numpy as np
import warnings
warnings.filterwarnings('ignore')
from sklearn.model_selection import train_test_split
#wczytanie modelu
rf = pickle.load(open("./Modele/random_forest", 'rb'))
# wczytanie zbioru danych
data = pd.read_csv("./heart_data.csv")
data.head()
| age | sex | trestbps | chol | fbs | restecg | thalach | exang | oldpeak | ca | ... | thal_fd | thal_rd | slope_up | slope_flat | slope_down | cp_ta | cp_aa | cp_np | cp_a | target | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 63 | 1 | 145 | 233 | 1 | 1 | 150 | 0 | 2.3 | 0 | ... | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 0 |
| 1 | 67 | 1 | 160 | 286 | 0 | 1 | 108 | 1 | 1.5 | 3 | ... | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 1 |
| 2 | 67 | 1 | 120 | 229 | 0 | 1 | 129 | 1 | 2.6 | 2 | ... | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 1 |
| 3 | 37 | 1 | 130 | 250 | 0 | 0 | 187 | 0 | 3.5 | 0 | ... | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 | 0 | 0 |
| 4 | 41 | 0 | 130 | 204 | 0 | 1 | 172 | 0 | 1.4 | 0 | ... | 0 | 0 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
5 rows × 21 columns
# odzielenie targetu od innych zmiennych
y = data.target.values
x = data.drop(['target'], axis = 1)
x_train, x_test, y_train, y_test = train_test_split(x,y, test_size = 0.2,random_state=0, stratify=y)
# stworzenie explainera
explainer = dx.Explainer(rf, x_train, y_train)
Preparation of a new explainer is initiated -> data : 242 rows 20 cols -> target variable : 242 values -> model_class : sklearn.ensemble._forest.RandomForestClassifier (default) -> label : Not specified, model's class short name will be used. (default) -> predict function : <function yhat_proba_default at 0x000001AB89A37E50> will be used (default) -> predict function : Accepts pandas.DataFrame and numpy.ndarray. -> predicted values : min = 0.0322, mean = 0.46, max = 0.988 -> model type : classification will be used (default) -> residual function : difference between y and yhat (default) -> residuals : min = -0.738, mean = -0.00088, max = 0.81 -> model_info : package sklearn A new explainer has been created!
Sprawdźmy jak działa nasz explainer w praktyce. Wybierzmy pierwszą obserwację w zbiorze danych oraz wyliczmy dla niej predykcję modelu.
x_train.iloc[0,:]
age 56.0 sex 1.0 trestbps 125.0 chol 249.0 fbs 1.0 restecg 1.0 thalach 144.0 exang 1.0 oldpeak 1.2 ca 1.0 thal_n 1.0 thal_fd 0.0 thal_rd 0.0 slope_up 0.0 slope_flat 1.0 slope_down 0.0 cp_ta 1.0 cp_aa 0.0 cp_np 0.0 cp_a 0.0 Name: 111, dtype: float64
Charakterystyka wybranego pacjenta (kilka wyróżniających się zmiennych):
Po przyjrzeniu się danym możemy przypuszczać, że mężczyzna ten posiada chorobę wieńcową. Wartość targetu = 1 potwierdza nasze przypuszczenia.
y_train[0]
1
Predykcja modelu wynosi natomiast około 0.758.
explainer.predict(x_train)[0]
0.7583198920657599
Profil Ceteris Paribus dla tej obserwacji:
cp = explainer.predict_profile(x_train.iloc[0,:])
cp.plot()
Calculating ceteris paribus: 100%|█████████████████████████████████████████████████████| 20/20 [00:01<00:00, 13.44it/s]
Wnioski:
Pierwszą obserwacją będzie ta analizowana wyżej. Postaramy znaleźć się taką obserwację, której profil CP będzie się różnić od poprzedniej.
x_train.iloc[7,:]
age 53.0 sex 0.0 trestbps 130.0 chol 264.0 fbs 0.0 restecg 1.0 thalach 143.0 exang 0.0 oldpeak 0.4 ca 0.0 thal_n 1.0 thal_fd 0.0 thal_rd 0.0 slope_up 0.0 slope_flat 1.0 slope_down 0.0 cp_ta 1.0 cp_aa 0.0 cp_np 0.0 cp_a 0.0 Name: 81, dtype: float64
y_train[7]
0
Charakterystyka wybranego pacjenta (kilka wyróżniających się zmiennych):
Profil Ceteris Paribus dla tej obserwacji:
cp2 = explainer.predict_profile(x_train.iloc[7,:])
cp2.plot()
Calculating ceteris paribus: 100%|█████████████████████████████████████████████████████| 20/20 [00:01<00:00, 12.08it/s]
Wnioski (wskazanie najważniejszych różnic pomiędzy profilami CP):